前言
学习应为input
和output
相结合之过程,这就是写这篇文章的原因。
在大屏幕展示web APP中,经常会用到滚动列表。经过几次尝试,确定了一个还不错的思路。
需求
- 列表表头thead部分静止,而tbody部分向上滚动。
- tbody部分滚动结束之后,需要刷新数据,最终效果是以向上滚动的形式将数据库中全部相关数据展示出来。
分析
如果数据量比较小的话,我们完全可以将数据一次性全部拿出来,放到DOM中进行循环滚动。实际就是类似轮播图的效果。
但若有很多数据的话,这样做很可能造成内存泄露。自然,我们可以想到将列表数据分页。我最初的想法是,在table
的外层放一个div
作为容器,然后table
定时向上增加top
值,等table
跑了一半时,向后端请求数据,动态创建一个组件tbody
插入到table
中,然后等前面一个tbody
走完时(看不见了),将这个组件删除。该想法看起来可行的,但是实践中遇到了不少麻烦。在删除前面的组件时,会导致table
的高度减小,表格瞬间掉下去了。这显然不是我们想要的,副作用挺大的。
既然这样,我把tbody
分开到两个table
里,两个table
循环。当前一个table
下面没有数据时,第二个table
开始走,等第一个table
完全走出div
,将它位置重置到div
的下面,并更新数据,然后重复之间的动作。完成起来稍微有点麻烦,不过效果还说得过去,差强人意。问题是,两个定时器不稳定,打开其他软件,再回来时,两个table跑的不一致了。这个先天性疾病,setInterval
就是不够精确的,两个定时器一起容易出现配合不好的情况。
最终,在下班回家的路上,我想到了一个不需要两个table的方法。只用一个table
定时上移,走完一半时,清除定时器,重置位置,并更新一半的数据。也就是去除数组中前一半数据,将后台拉过来的新数据拼接在数组上。这样就可以实现数据的持续刷新,并且table
看起来是一直往上走的。
代码
scroll-table.component.html
<div class="table-container">
<table class="head-show">
<thead>
<tr>
<th style="width:12.8%;">字段1</th>
<th style="width:12.8%;">字段2</th>
<th>字段3</th>
<th style="width:12.8%;">字段4</th>
</tr>
</thead>
</table>
<div class="scroller-container">
<table #scroller class="scroller">
<tbody>
<tr *ngFor="let ele of tbody">
<td style="width:12.8%;">{{ele.field01}}</td>
<td style="width:12.8%;">{{ele.field02}}</td>
<td><div>{{ele.field03}}</div></td>
<td style="width:12.8%;">{{ele.field04}}</td>
</tr>
</tbody>
</table>
</div>
</div>
scroll-table.component.ts
import { Component, OnInit, ViewChild, ElementRef, Input } from '@angular/core';
import { HttpService } from '../http.service';
@Component({
selector: 'app-scroll-table',
templateUrl: './scroll-table.component.html',
styleUrls: ['./scroll-table.component.scss']
})
export class ScrollTableComponent implements OnInit {
tbody: any = [];
@Input() url; //将地址变成组件的一个参数,也就是输入属性
//控制滚动的元素
@ViewChild('scroller') scrollerRef: ElementRef;
timer: any;
freshData: any;
pageNow = 1;//pageNow是当前数据的页码,初始化为1
constructor(private http: HttpService) {}
ngOnInit() {
//初始化拿到native
let scroller: HTMLElement = this.scrollerRef.nativeElement;
this.http.sendRequest(this.url).subscribe((data :any[]) => {
this.tbody = data.concat(data);
});
//开启定时器
this.timer = this.go(scroller);
}
getFreshData() {
//每次请求数据时,pageNow自增1
this.http.sendRequest(`${this.url}?pageNow=${++this.pageNow}`).subscribe((data:any[]) => {
if(data.length<10) {
//数据丢弃,pageNow重置为1
this.pageNow = 1;
}
this.freshData = data;
});
}
go(scroller) {
var
moved = 0,
step = -50,
timer = null,
task = () => {
let style = document.defaultView.getComputedStyle(scroller, null);
let top = parseInt(style.top, 10);
if (moved < 10) {
if(moved===0) {
this.getFreshData();
}
scroller.style.transition = "top 0.5s ease";
moved++;
scroller.style.top = top + step + 'px';
} else {
//重置top,moved,清除定时器
clearInterval(timer);
moved = 0;
scroller.style.transition = "none";
scroller.style.top = '0px';
//更新数据
this.tbody = this.tbody.slice(10).concat(this.freshData);
timer = setInterval(task,1000);
}
};
timer = setInterval(task, 1000);
}
}
scroll-table.component.scss
.table-container {
width: 100%;
height: 100%;
}
.head-show {
border-top: 1px solid #4076b9;
height: 11.7%;
}
.scroller-container {
border-bottom: 1px solid #4076b9;
//border: 1px solid #fff;
width: 100%;
//height: 88.3%;
height: 250px;
box-sizing: border-box;
overflow: hidden;
position:relative;
.scroller {
position: absolute;
top:0;
left:0;
transition: top .5s ease;
}
}
table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
//border-bottom:1px solid #4076b9;
th {
border-bottom:1px dashed #2d4f85;
color:#10adda;
padding:8px 2px;
font-size: 14px;
}
td {
border-bottom: 1px dashed #2d4f85;
font-size: 12px;
color:#10adda;
position: relative;
height: 49px;
div{
padding:0 2px;
box-sizing: border-box;
text-align:center;
display: table-cell;
overflow: hidden;
vertical-align: middle;
}
//border-width:1px 0 ;
}
}
这样实现的效果是,该组件只需要传入一个参数url
,然后所有的操作、包括更新数据,全部由组件自身完成。从而完成了组件的封装,便于复用。
总结和思考
1、更新数据应该放在源头更新,也就是说,不要去添加和删除DOM元素,这样操作麻烦,性能也低。放在源头的意思是,在组件类中存储展示数据的那个数组上做文章。
2、后台请求新数据应该提早准备就绪,放在另一个临时数组中。它相当于一个缓存,一个暂存器。
3、我将组件想象成一个函数,它只有一个参数,就是数据的地址,只要有这个参数,组件就能正常工作,不依赖于其他任何值。松耦合性。
4、加强函数式编程思想,虽然这是React
的特色,但我总觉得angular
也可以的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。